﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace FTI.Core.Threading
{
    public class ThreadPool
    {
        private object syncLock = new object();
        private object criticalLock = new object();

        private readonly static ThreadPool _singleton = new ThreadPool();

        public static ThreadPool Instance
        {
            get
            {
                return _singleton;
            }
        }

        private enum StateType
        {
            stopped,
            started,
            completed,
            aborted
        }

        private int
            MAX = -1,
            MIN = -1,
            MIN_WAIT = 10, // milliseconds
            MAX_WAIT = 15000, // milliseconds threshold for abitrary tasks
            CLEANUP_INTERVAL = 60000,
            SCHEDULING_INTERVAL = 10;

        private
            Queue<TaskHandle> ReadyQueue = null;
        private
            List<TaskItem> Pool = null;
        private
            Thread TaskScheduler = null;

        private
            DateTime LastCleanup;

        private class TaskHandle
        {
            public ClientHandle Token { get; set; }
            public Task TheTask { get; set; }
            public Action<Status> Callback { get; set; }

            internal void Cancel()
            {
                Token = null;
                TheTask = null;
                Callback = null;
            }
        }

        private class TaskItem
        {
            public TaskItem()
            {
                State = StateType.stopped;
                StartTime = null;
            }

            public TaskHandle TaskHandle { get; set; }
            public Thread Handler { get; set; }
            public StateType State { get; set; }
            public DateTime? StartTime { get; set; }

            internal void Abort()
            {
                State = StateType.aborted;
                TaskHandle.Callback = null;

                try
                {
                    Handler.Priority = ThreadPriority.Lowest;
                    Handler.IsBackground = true;
                    Handler.Abort();
                }
                catch { }
            }
        }

        private ThreadPool()
        {
            MAX = Environment.ProcessorCount;
            MIN = ((Decimal)Environment.ProcessorCount / 3) < 1 ? 1 : Math.Ceiling((Decimal)Environment.ProcessorCount / 3).ToInteger();

            InitializeThreadPool();
        }

        private void InitializeThreadPool()
        {
            ReadyQueue = new Queue<TaskHandle>();
            Pool = new List<TaskItem>();
            LastCleanup = DateTime.Now;

            #region initialize pool with minimum capacity
            for (int i = 0; i <= MIN; i++)
            {
                TaskItem oItem = new TaskItem()
                {
                    TaskHandle = new TaskHandle()
                    {
                        TheTask = () => { },
                        Callback = (status) => { },
                        Token = new ClientHandle()
                    },
                    State = StateType.completed
                };
                AddTaskToPool(oItem);
            }
            #endregion

            TaskScheduler = new Thread(() =>
            {
                bool
                    added = false;

                do
                {
                    lock (syncLock)
                    {
                        int
                            count = ReadyQueue.Count;

                        for (int i = 0; i < count; i++)
                        {
                            TaskHandle readyItem = ReadyQueue.Peek();

                            if (readyItem.TheTask == null)
                            {   // if the task has been canceled, set to null, remove the readyItem from the queue.
                                ReadyQueue.Dequeue();
                                continue;
                            }

                            added = false;

                            lock (criticalLock)
                            {
                                foreach (TaskItem oItem in Pool)
                                {
                                    lock (oItem)
                                    {
                                        if (oItem.State == StateType.completed)
                                        {
                                            oItem.TaskHandle = readyItem;
                                            oItem.State = StateType.stopped;
                                            added = true;
                                            break;
                                        }
                                    }
                                }

                                if (!added && Pool.Count < MAX)
                                {
                                    TaskItem oItem = new TaskItem()
                                    {
                                        TaskHandle = readyItem,
                                        State = StateType.stopped
                                    };
                                    AddTaskToPool(oItem);
                                    added = true;
                                }

                                if (!added)
                                {
                                    break;
                                }

                                ReadyQueue.Dequeue();
                            }
                        }
                        if ((DateTime.Now - LastCleanup) > TimeSpan.FromMilliseconds(CLEANUP_INTERVAL))
                        {
                            Cleanup();
                            LastCleanup = DateTime.Now;
                        }
                    }
                    Thread.Yield();
                } while (true);
            });
            TaskScheduler.Priority = ThreadPriority.AboveNormal;
            TaskScheduler.Start();
        }

        private void AddTaskToPool(TaskItem taskItem)
        {
            taskItem.Handler = new Thread(() =>
            {
                do
                {
                    bool enter = false;

                    lock (taskItem)
                    {
                        if (taskItem.State == StateType.aborted)
                        {
                            break;
                        }

                        if (taskItem.State == StateType.stopped)
                        {
                            taskItem.State = StateType.started;
                            taskItem.StartTime = DateTime.Now;
                            enter = true;
                        }
                    }

                    if(enter)
                    {
                        Status taskStatus = new Status();
                        try
                        {
                            taskItem.TaskHandle.TheTask.Invoke();
                            taskStatus.Success = true;
                        }
                        catch(Exception pEx)
                        {
                            taskStatus.Success = false;
                            taskStatus.InnerException = pEx;
                        }

                        lock (taskItem)
                        {
                            if (taskItem.State != StateType.aborted)
                            {
                                try
                                {
                                    taskItem.State = StateType.completed;
                                    taskItem.StartTime = null;

                                    if (taskItem.TaskHandle.Callback != null)
                                    {
                                        taskItem.TaskHandle.Callback(taskStatus);
                                    }
                                }
                                catch
                                { }
                            }
                        }
                    }
                    Thread.Yield();
                } while (true);
            });

            taskItem.Handler.Start();

            lock (criticalLock)
            {
                Pool.Add(taskItem);
            }
        }

        private void Cleanup()
        {
            List<TaskItem> oFilteredItems = null;

            lock(criticalLock)
            {
                oFilteredItems = Pool
                    .Where(X =>
                        (X.TaskHandle.Token.IsArbitrary && X.StartTime.HasValue) &&
                        (DateTime.Now - X.StartTime.Value) > TimeSpan.FromMilliseconds(MAX_WAIT))
                    .ToList();
            }
            foreach(TaskItem oItem in oFilteredItems)
            {
                CancelTask(oItem.TaskHandle.Token);
            }
            lock(criticalLock)
            {
                oFilteredItems = Pool
                    .Where(X => X.State == StateType.aborted)
                    .ToList();

                foreach(TaskItem oItem in oFilteredItems)
                {
                    oItem.Abort();

                    Pool.Remove(oItem);
                }

                while(Pool.Count > MIN)
                {
                    oFilteredItems = Pool
                        .Where(X => X.State == StateType.completed)
                        .ToList();

                    if (oFilteredItems.Count == 0)
                    {
                        break;
                    }

                    foreach(TaskItem oItem in oFilteredItems)
                    {
                        if (Pool.Count <= MIN)
                        {
                            break;
                        }

                        oItem.Abort();

                        Pool.Remove(oItem);
                    }
                }

                for(int i = Pool.Count; i <= MIN; i++)
                {
                    TaskItem oItem = new TaskItem()
                    {
                        TaskHandle = new TaskHandle()
                        {
                            TheTask = () => { },
                            Callback = (status) => { },
                            Token = new ClientHandle()
                        },
                        State = StateType.completed
                    };
                    AddTaskToPool(oItem);
                }
            }
        }

        #region public interface
        public ClientHandle QueueTask(Task task, Action<Status> callback)
        {
            TaskHandle oHandle = new TaskHandle()
            {
                TheTask = task,
                Token = new ClientHandle(),
                Callback = callback
            };
            
            lock (syncLock)
            {
                ReadyQueue.Enqueue(oHandle);
            }

            return oHandle.Token;
        }

        public static void CancelTask(ClientHandle handle)
        {
            lock(Instance.syncLock)
            {
                TaskHandle oHandle = Instance.ReadyQueue.FirstOrDefault(X => X.Token.ID.Equals(handle.ID));

                if(oHandle != null)
                {
                    oHandle.Cancel();
                }
                else
                {
                    TaskItem oItem = null;

                    lock(Instance.criticalLock)
                    {
                        oItem = Instance.Pool.FirstOrDefault(X => X.TaskHandle.Token.ID.Equals(handle.ID));
                    }

                    if(oItem != null)
                    {
                        lock(oItem)
                        {
                            if(oItem.State != StateType.completed)
                            {
                                oItem.Abort();
                            }
                        }
                    }
                }
            }
        }

        public static void Wait()
        {
            List<TaskItem> oFilteredItems = null;

            do
            {
                int
                    NumberOfRunningThreads = 0;

                lock(Instance.criticalLock)
                {
                    NumberOfRunningThreads = Instance.Pool.Count(X => X.State == StateType.started);
                }

                if (NumberOfRunningThreads > 0 || Instance.ReadyQueue.Count > 0)
                {
                    Thread.Sleep(Instance.MIN_WAIT);
                }
                else
                {
                    break;
                }
            } while (true);
        }
        #endregion
    }
}
